<?php
/**
 * DSPHP入口
 * 
 * Copyright (c) 2014~2019 https://desion.net All rights reserved.
 * Author: 阿林 QQ:120867629 <linhong00@gmail.com>
 */
declare (strict_types = 1);
namespace dsphp;
use dsphp\Container;
use dsphp\App;

error_reporting(E_ERROR | E_WARNING | E_PARSE);
if(version_compare(PHP_VERSION,'7.3.0','<'))  die('require PHP > 7.3.0 !');
date_default_timezone_set('PRC'); // Asia/Shanghai

define('DSPHP_EXT',  '.php');
define('DSPHP_PATH', realpath(__DIR__ . '/../') . DIRECTORY_SEPARATOR);
defined('DSPHP_WEB') or define('DSPHP_WEB', 'web');

defined('APP_CONF')  or define('APP_CONF', 'config');

// 加载核心类
require_once DSPHP_PATH . 'dsphp'.DIRECTORY_SEPARATOR.'Loader.php';
Loader::register();

// 入口类
class Dsphp
{
    // 版本
    const VERSION  = '0.3.1';
    // 应用
    protected $app;
    // 日志
    protected $log = null;
    // 应用开始时间
    protected $beginTime;
    // 应用内存初始占用
    protected $beginMem;
    // 配置信息
    protected $config;

    /**
     * 架构方法
     */
    public function __construct()
    {
        // 初始化容器
        $list = [
            'app'      => App::class,
            'cache'    => Cache::class,
            'config'   => Config::class,
            'db'       => Db::class,
            'log'      => Log::class,
            'http'     => Http::class,
            'upload'   => Upload::class,
        ];

        Container::init($list);
        
        $this->app = new App();
        $this->app->set('app', $this->app);
    }

    /**
     * 执行请求
     */
    public function run()
    {
        $this->initialize();
        // 创建http
        $http = $this->app->make('http', [], true);
        $this->app->set('http', $http);
        
        $this->log->info('HTTP loaded');
        // 执行和结束
        $pathinfo = $http->pathinfo(false);
        $pathinfo = $this->paseRoute($pathinfo);
        // 分割参数
        $controller = strip_tags($pathinfo[0] ?: $this->config['default_controller']);
        $param      = count($pathinfo) > 2 ? array_slice($pathinfo, 2) : [];
        $action     = strip_tags($pathinfo[1] ?: $this->config['default_action']);

        try {
            $controller = $this->controller($controller, $action);
            $content    = $this->action($controller, $action, $param);
            
            $http->send($content);
        } catch (Throwable $e) {
            throw new \Exception($e);
        }

        // 写入日志
        $this->log->save();
    }

    /**
     * 解析路由
     *
     * @param string $pathinfo
     */
    protected function paseRoute(string $pathinfo):array
    {
        $path = '';
        foreach ($this->route as $route) {
            preg_match_all($route['info'], $pathinfo, $info);
            if (empty($info[0])) {
                continue;
            }

            if ($route['action'] == '<reg>') {
                $path .= $route['controller'];
                $path .= '/' . $info[1][0] . '/';
            } else {
                $path .= str_replace('<reg>', $info[1][0], $route['controller']);
                $path .= '/' .$route['action'];
            }

            $exts = $route['exts'] ?? '';
            if ($exts) {
                $path .= '/' . $exts;
            }

            if (count($info) > 2) {
                $info = array_slice($info, 2);
                
                foreach ($info as $val) {
                    $path .= '/' . $val[0];
                }
            }
            // 符合强制退出
            break;
        }

        if ($path == '') {
            $path = $pathinfo;
        }
        
        return explode('/', $path); 
    }

    /**
     * 获取当前的控制器并实例化
     * 
     * @param  array $pathinfo
     * @return object
     */
    protected function controller(string $controller, string $action)
    {
        $reg = '/^[a-z\d]{2,8}(\.[a-z\d]{2,8})*$/';
        $reg = preg_match($reg, $controller);
        if (! $reg) {
            throw new \Exception(404, 'controller error:'.$controller);
        }

        $class = $this->app->parseClass('controller', $controller);

        $empty = $this->config['empty_controller'] ?: 'Error';
        $empty = $this->app->parseClass('controller', $empty);
        
        if (class_exists($class)) {
            //
        } elseif (class_exists($empty)) {
            $class = $empty;
        } else {
            throw new \Exception(404, 'controller not exists:' . $class);
        }

        $config = $this->app->getConfig(DSPHP_WEB);
        $config['app_debug'] = $this->config['app_debug'];
        
        $param = [
            'controller' => $controller, 
            'action'     => $action, 
            'config'     => $config
        ];

        return $this->app->make($class, [$param], true);
    }

    /**
     * 获取当前的操作名
     *
     * @param  array $pathinfo
     */
    protected function action($controller, string $action, array $param): string
    {
        $empty  = $this->config['empty_action'];
        
        $reg  = '/^[a-z]{3,16}(_[a-z]{3,16})*$/';
        $reg  = preg_match($reg, $action);
        if (! $reg) {
            throw new \Exception(404, 'action error:'.$action);
        }

        if (method_exists($controller, $action)) {
            //
        } elseif (method_exists($controller, $empty)) {
            $action = $empty;
        } else {
            throw new \Exception(404, 'action not exists:' . $action);
        }
        
        $result = $this->app->invokeMethod([$controller, $action], $param);
        $result = is_null($result) ? '' : $result;
        return $result;
    }

    /**
     * 获取框架版本
     */
    public function version(): string
    {
        return static::VERSION;
    }

    /**
     * 初始化应用
     *
     * @return $this
     */
    public function initialize()
    {
        $this->beginTime = microtime(true);
        $this->beginMem  = memory_get_usage();
        // 初始化配置类和加载配置，并返回框架配置
        $this->_configInit();
        // 调试模式设置
        $this->_debugInit();
        // 用户全局函数
        $this->_funcInit();
        // 初始化日志类
        $this->_logInit();
        // 加载路由
        $this->route = $this->_loadRoute();

        $this->log->info('DSPHP initialized');
    }

    /**
     * 初始化配置类和加载配置，并返回框架配置
     */
    private function _configInit()
    {
        // 初始化配置类和加载配置，并返回框架配置
        $config = $this->app->getConfigPath(APP_CONF);
        $this->app->make('config', [$config]);
        // 加载框架配置，并设置测试状态
        $this->config = $this->app->getConfig('dsphp');
    }

    /**
     * 调试模式设置
     */
    private function _debugInit(): void
    {
        // 应用调试模式
        if (! $this->config['app_debug']) {
            ini_set('display_errors', 'Off');
        }

        if (! $this->runningInConsole()) {
            //重新申请一块比较大的buffer
            if (ob_get_level() > 0) {
                $output = ob_get_clean();
            }
            ob_start();
            if (! empty($output)) {
                echo $output;
            }
        }
    }

    /**
     * 加载自定义公用函数
     */
    private function _funcInit():void
    {
        $path = $this->app->getAppPath();
        $func = glob($path . 'common' . DIRECTORY_SEPARATOR . '*'.DSPHP_EXT);
        foreach ($func as $file) {
            include $file;
        }
    }

    /**
     * 初始化日志类
     */ 
    private function _logInit()
    {
        $config = $this->app->getConfig('log');
        $config['runtime'] = $this->app->getRuntimePath();

        $log = $this->app->make('log', [$config]);
        $log->init($this->config['app_debug'], $this->beginMem, $this->beginTime);
        // 更新容器
        //$this->app->set('log', $log);

        $this->log = $log;
    }

    /**
     * 是否运行在命令行下
     * @return bool
     */
    public function runningInConsole()
    {
        return php_sapi_name() === 'cli' || php_sapi_name() === 'phpdbg';
    }

    /**
     * 加载路由
     */
    private function _loadRoute() :array
    {
        $status = $this->config['app_route'] ?? false;
        if (! $status) {
            return [];
        }

        $file  = $this->app->getClassPath();
        $file .= 'route' . DSPHP_EXT;
        if (! is_file($file)) {
            return [];
        }
        
        $file  = include $file;
        $route = [];
        foreach ($file as $path => $list) {
            foreach ($list as $func => $info) {
                $temp  = '/^'.$path.'\/('.$info['reg'].')';
                $param = $info['param'] ?? '';
                $count = $info['count'] ?? 1;
                if ($param) {
                    $reg = '\/('.$param.')';
                    for ($i = 1; $i <= $count; $i++) {
                        $temp .= '\/('.$param.')';
                    }
                }

                $temp .= '$/';

                $info['info'] = $temp;
                $route[] = $info;
            }
        }

        return $route;
    }
}

return new Dsphp();